/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

namespace facebook {
namespace jni {

namespace detail {

template <typename E>
struct IteratorHelper : public JavaClass<IteratorHelper<E>> {
  constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/IteratorHelper;";

  typedef local_ref<E> value_type;
  typedef ptrdiff_t difference_type;
  typedef value_type* pointer;
  typedef value_type& reference;
  typedef std::forward_iterator_tag iterator_category;

  typedef JavaClass<IteratorHelper<E>> JavaBase_;

  bool hasNext() const {
    static auto hasNextMethod =
        JavaBase_::javaClassStatic()->template getMethod<jboolean()>("hasNext");
    return hasNextMethod(JavaBase_::self());
  }

  value_type next() {
    static auto elementField =
        JavaBase_::javaClassStatic()->template getField<jobject>("mElement");
    return dynamic_ref_cast<E>(JavaBase_::getFieldValue(elementField));
  }

  static void reset(value_type& v) {
    v.reset();
  }
};

template <typename K, typename V>
struct MapIteratorHelper : public JavaClass<MapIteratorHelper<K, V>> {
  constexpr static auto kJavaDescriptor =
      "Lcom/facebook/jni/MapIteratorHelper;";

  typedef std::pair<local_ref<K>, local_ref<V>> value_type;

  typedef JavaClass<MapIteratorHelper<K, V>> JavaBase_;

  bool hasNext() const {
    static auto hasNextMethod =
        JavaBase_::javaClassStatic()->template getMethod<jboolean()>("hasNext");
    return hasNextMethod(JavaBase_::self());
  }

  value_type next() {
    static auto keyField =
        JavaBase_::javaClassStatic()->template getField<jobject>("mKey");
    static auto valueField =
        JavaBase_::javaClassStatic()->template getField<jobject>("mValue");
    return std::make_pair(
        dynamic_ref_cast<K>(JavaBase_::getFieldValue(keyField)),
        dynamic_ref_cast<V>(JavaBase_::getFieldValue(valueField)));
  }

  static void reset(value_type& v) {
    v.first.reset();
    v.second.reset();
  }
};

template <typename T>
class Iterator {
 public:
  typedef typename T::value_type value_type;
  typedef ptrdiff_t difference_type;
  typedef value_type* pointer;
  typedef value_type& reference;
  typedef std::input_iterator_tag iterator_category;

  // begin ctor
  Iterator(global_ref<typename T::javaobject>&& helper)
      : helper_(std::move(helper)), i_(-1) {
    ++(*this);
  }

  // end ctor
  Iterator() : i_(-1) {}

  bool operator==(const Iterator& it) const {
    return i_ == it.i_;
  }
  bool operator!=(const Iterator& it) const {
    return !(*this == it);
  }
  const value_type& operator*() const {
    assert(i_ != -1);
    return entry_;
  }
  const value_type* operator->() const {
    assert(i_ != -1);
    return &entry_;
  }
  Iterator& operator++() { // preincrement
    bool hasNext = helper_->hasNext();
    if (hasNext) {
      ++i_;
      entry_ = helper_->next();
    } else {
      i_ = -1;
      helper_->reset(entry_);
    }
    return *this;
  }
  Iterator operator++(int) { // postincrement
    Iterator ret;
    ret.i_ = i_;
    ret.entry_ = std::move(entry_);
    ++(*this);
    return ret;
  }

  global_ref<T> helper_;
  // set to -1 at end
  std::ptrdiff_t i_;
  value_type entry_;
};

} // namespace detail

template <typename E>
struct JIterator<E>::Iterator
    : public detail::Iterator<detail::IteratorHelper<E>> {
  using detail::Iterator<detail::IteratorHelper<E>>::Iterator;
};

template <typename E>
typename JIterator<E>::Iterator JIterator<E>::begin() const {
  static auto ctor =
      detail::IteratorHelper<E>::javaClassStatic()
          ->template getConstructor<typename detail::IteratorHelper<
              E>::javaobject(typename JIterator<E>::javaobject)>();
  return Iterator(
      make_global(detail::IteratorHelper<E>::javaClassStatic()->newObject(
          ctor, this->self())));
}

template <typename E>
typename JIterator<E>::Iterator JIterator<E>::end() const {
  return Iterator();
}

template <typename E>
struct JIterable<E>::Iterator
    : public detail::Iterator<detail::IteratorHelper<E>> {
  using detail::Iterator<detail::IteratorHelper<E>>::Iterator;
};

template <typename E>
typename JIterable<E>::Iterator JIterable<E>::begin() const {
  static auto ctor =
      detail::IteratorHelper<E>::javaClassStatic()
          ->template getConstructor<typename detail::IteratorHelper<
              E>::javaobject(typename JIterable<E>::javaobject)>();
  return Iterator(
      make_global(detail::IteratorHelper<E>::javaClassStatic()->newObject(
          ctor, this->self())));
}

template <typename E>
typename JIterable<E>::Iterator JIterable<E>::end() const {
  return Iterator();
}

template <typename E>
size_t JCollection<E>::size() const {
  static auto sizeMethod =
      JCollection<E>::javaClassStatic()->template getMethod<jint()>("size");
  return sizeMethod(this->self());
}

template <typename E>
bool JCollection<E>::add(alias_ref<E> elem) {
  static auto addMethod =
      JCollection<E>::javaClassStatic()
          ->template getMethod<jboolean(alias_ref<JObject>)>("add");
  return addMethod(this->self(), elem);
}

template <typename K, typename V>
struct JMap<K, V>::Iterator
    : public detail::Iterator<detail::MapIteratorHelper<K, V>> {
  using detail::Iterator<detail::MapIteratorHelper<K, V>>::Iterator;
};

template <typename K, typename V>
size_t JMap<K, V>::size() const {
  static auto sizeMethod =
      JMap<K, V>::javaClassStatic()->template getMethod<jint()>("size");
  return sizeMethod(this->self());
}

template <typename K, typename V>
typename JMap<K, V>::Iterator JMap<K, V>::begin() const {
  static auto ctor =
      detail::MapIteratorHelper<K, V>::javaClassStatic()
          ->template getConstructor<
              typename detail::MapIteratorHelper<K, V>::javaobject(
                  typename JMap<K, V>::javaobject)>();
  return Iterator(
      make_global(detail::MapIteratorHelper<K, V>::javaClassStatic()->newObject(
          ctor, this->self())));
}

template <typename K, typename V>
typename JMap<K, V>::Iterator JMap<K, V>::end() const {
  return Iterator();
}

template <typename K, typename V>
local_ref<JObject> JMap<K, V>::put(alias_ref<K> key, alias_ref<V> val) {
  static auto putMethod =
      JMap<K, V>::javaClassStatic()
          ->template getMethod<JObject(alias_ref<JObject>, alias_ref<JObject>)>(
              "put");
  return putMethod(this->self(), key, val);
}

template <typename E>
local_ref<JArrayList<E>> JArrayList<E>::create() {
  return JArrayList<E>::newInstance();
}

template <typename E>
local_ref<JArrayList<E>> JArrayList<E>::create(int initialCapacity) {
  return JArrayList<E>::newInstance(initialCapacity);
}

template <typename E>
local_ref<JHashSet<E>> JHashSet<E>::create() {
  return JHashSet<E>::newInstance();
}

template <typename E>
local_ref<JHashSet<E>> JHashSet<E>::create(int initialCapacity) {
  return JHashSet<E>::newInstance(initialCapacity);
}

template <typename K, typename V>
local_ref<JHashMap<K, V>> JHashMap<K, V>::create() {
  return JHashMap<K, V>::newInstance();
}

template <typename K, typename V>
local_ref<JHashMap<K, V>> JHashMap<K, V>::create(int initialCapacity) {
  return JHashMap<K, V>::newInstance(initialCapacity);
}

} // namespace jni
} // namespace facebook
